//----BEGIN LICENSE----//
/*****************************************************************\
** prevalentLite - sqlite3 persistence object library 
**
** Copyright (C) 2008 Philip R. Schuchardt
**
** This program is free software: you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation, either version 3 of the License, or
** (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program.  If not, see <http://www.gnu.org/licenses/>.
**
*****************************************************************/
//----END LICENSE----//

///////////////////////////////////////////////////////////////////////////////
/*!
\class plDatabase plDatabase.h <plDatabase>

\brief The heart of prevalentLite. It uses an sqlite3 database to load and save information

WARNING: There should only be one instance of plDatabase existing in your
program at a time.  Never call new plDatabase more once unless you delete
the first plDatabase.  Never use any plObject's that were using the first database

WARNING: Before using any plObject's make sure you call plDatabase::Open(my_database_filename).

WARNING: plDatabase is not thread safe.

plDatabase allows you to indirectly load and save plObject's.

Simple Example:
\code
//Create a new database and open a file
plDatabase* database = new plDatabase();
database->Open("myFile.db");

//Create a new object in the database
MyObj* obj = new MyObj() //Subclass of plObject

//Start using your plObject classes
obj->SetSomeValue(5);

//Delete myObj from memory but not from the databases memory
obj->DeleteMemory();

//Get all the MyObj objects
std::vector<MyObj*> vectorObjs;
database->GetAllObjects<MyObj>(vectorObjs);

vectorMyObjs.size(); //Should size = 1
MyObj* obj2 = vectorMyObjs.at(0); //Should return a copy of obj

//Close the database and the file
delete database;

//Don't use any of your plObject classes

\endcode


*/
///////////////////////////////////////////////////////////////////////////////

//debug
#include <iostream>

//STL
#include <fstream>
#include <string>

//our includes
#include "plDatabase.h"
#include "plObject.h"
#include "plMemory.h"
#include "plData.h"
#include "plFactory.h"

//Statics
plDatabase* plDatabase::GlobalDatabase = NULL;

//////////////////////////////////////////////////
/*!
plDatabase()
\brief Constructor

WARNING: You should only create one have one plDatabase in existance in our whole program at a time.

The sqlite3 database isn't open until you call plDatabase::Open();
*/
//////////////////////////////////////////////////
plDatabase::plDatabase() {
    Database = NULL;
    GlobalDatabase = this;

    InTransaction = false;
}

//////////////////////////////////////////////////
/*!
    ~plDatabase()
    \brief Destructor

    Closes the sqlite3 database.

    WARNING: If any of your plObject's still exist do not use them.
*/
//////////////////////////////////////////////////
plDatabase::~plDatabase() {
    sqlite3_close(Database);
    Database = NULL;
    GlobalDatabase = NULL;
}

////////////////////////////////////////////////////
/*!
Open(const std::string& filename)
\brief Opens a new database

If this database currently haves a file open it will close the current file
and open a new one.

WARNING: Don't use any existing plObjects objects after you call this function.  They may corrupt your database.

\param filename The filename of where files will be store
*/
///////////////////////////////////////////////////
int plDatabase::Open(const std::string& filename) {

    CantWrite = false;

   // std::cout << "plDatabase Opening:" << filename << std::endl;

    int error = sqlite3_open(filename.c_str(), &Database);

    if (error == SQLITE_OK) {
        error = ResetMemory();
    }

    OpenedFilename = filename;

    return error;
}

/////////////////////////////////////////////////////////
/*!
  \brief Copies the currenly open database to some other file

    If the new filename is equal to the filename passed to Open(), then this function will do nothing.  This function is very useful for implementing a recovery / save system.  Bacisilly you open the database to some temparay database.  When the user hits "Save", the database is copied to the new location. Doesn't copy if the filenames are the same.

  \param filename The filename the database will be to
  \return true if the database was succefully copied to the new file
*/
//////////////////////////////////////////////////////////
int plDatabase::CopyTo(const std::string& filename ) {

    //If the filenames are different
    if(filename.compare(OpenedFilename) == 0) {
        return false;
    }

    //Lock the and flush the database
    if(InTransaction) {
        //End the currently opened transaction
        End();
    }

    //I want to lock the database but it should be fine in single threaded mode.

    //Copy the file bit by bit
    std::ifstream ifs(OpenedFilename.c_str(), std::ios::binary);
    std::ofstream ofs(filename.c_str(), std::ios::binary);
    ofs << ifs.rdbuf();

    return true;
}

/////////////////////////////////////////////////////////
/*!
  \brief Closes the currently opened database

   WARNING: Currently not implemented
*/
////////////////////////////////////////////////////////
int plDatabase::Close() {
    return 0;
}

/////////////////////////////////////////////////////////
/*!
\brief Creates and object in the database

Updates the database memory table with the new object and adds the object to it's table.

\param object The plObject that will be added to database
*/
/////////////////////////////////////////////////////////
void plDatabase::AddObject(plObject* object) {

    //Don't run if object adding is disabled
    if (CantWrite) { return; }

    //Add the object to database memory
    AddToMemory(object);

    std::string className = object->_GetClassName();

    std::stringstream sql;
    sql << "INSERT into " << className << "(Id) VALUES(" << object->Id() << ")";

    int error = 0;

    sqlite3_stmt* statement;
    error = sqlite3_prepare_v2(Database, sql.str().c_str(), -1, &statement, NULL);
    if (error == SQLITE_OK) {
        error = sqlite3_step(statement); //execute the sql
        sqlite3_finalize(statement);

    } else {

        //Just incase there a weird error with the insert
        const char* insertErrorMsg = sqlite3_errmsg(Database);

        //Finalize the last statement
        sqlite3_finalize(statement);

        //Generate sql for creating a new table dynamcially
        std::stringstream sqlNewTable;
        sqlNewTable << "CREATE TABLE " << className << "(Id INTEGER PRIMARY KEY)";

        //Create a new table
        sqlite3_stmt* createTableStmt;
        int tableError = sqlite3_prepare_v2(Database, sqlNewTable.str().c_str(), -1, &createTableStmt, NULL);

        //If sql prepare was okay
        if (tableError == SQLITE_OK) {
            sqlite3_step(createTableStmt);

            //std::cout << "Created table for:" << object->_GetClassName() << std::endl;

            //Retry the the insert
            sqlite3_stmt* retryStatement;
            error = sqlite3_prepare_v2(Database, sql.str().c_str(), -1, &retryStatement, NULL);
            if (error == SQLITE_OK) {
                error = sqlite3_step(retryStatement); //execute the sql
            }
            sqlite3_finalize(retryStatement);
        }

        if (tableError != SQLITE_OK || error != SQLITE_DONE) {
            //Errors - The fuck it mode!!!
            std::cout << "ERROR::SQLite Database:Inserting New Object: " << insertErrorMsg << std::endl;
            std::cout << "ERROR::SQLITE Database:Creating New Table: " << tableError << " " << sqlite3_errmsg(Database) << std::endl;
        } //end if(error == SQLITE_OK)

        sqlite3_finalize(createTableStmt);

    } //end if(error == SQLITE_OK)
} //end CreateObject

//////////////////////////////////////////////////
/*!
\brief Creates a memory object in the database
\param object The object that will be added
*/
//////////////////////////////////////////////////
void plDatabase::CreateMemoryObject(plMemory* object) {

    if (CantWrite) { return; }

    std::string className = object->_GetClassName();

    std::stringstream sql;
    sql << "INSERT into " << className << "(ObjectId) VALUES(-1)";

    int error = 0;

    sqlite3_stmt* statement;
    error = sqlite3_prepare_v2(Database, sql.str().c_str(), -1, &statement, NULL);
    if (error == SQLITE_OK) {
        error = sqlite3_step(statement); //execute the sql

        sqlite_int64 id = sqlite3_last_insert_rowid(Database);
        object->SetId(id);

        sqlite3_finalize(statement);
    } else {

        //Just incase there a weird error with the insert
        const char* insertErrorMsg = sqlite3_errmsg(Database);

        //Finalize the last statement
        sqlite3_finalize(statement);

        //Generate sql for creating a new table dynamcially
        std::stringstream sqlNewTable;
        sqlNewTable << "CREATE TABLE " << className << "(Id INTEGER PRIMARY KEY AUTOINCREMENT, ObjectId INTEGER)";

        //Create a new table
        sqlite3_stmt* createTableStmt;
        int tableError = sqlite3_prepare_v2(Database, sqlNewTable.str().c_str(), -1, &createTableStmt, NULL);

        //If sql prepare was okay
        if (tableError == SQLITE_OK) {

            //std::cout << "Created plMemory table" << std::endl;

            sqlite3_step(createTableStmt);

            //Retry the the insert
            //CreateMemoryObject(object);

            sqlite3_stmt* retryInsert;
            error = sqlite3_prepare_v2(Database, sql.str().c_str(), -1, &retryInsert, NULL);
            if (error == SQLITE_OK) {
                error = sqlite3_step(retryInsert); //execute the sql

                sqlite_int64 id = sqlite3_last_insert_rowid(Database);
                object->SetId(id);
            }
            sqlite3_finalize(retryInsert);

        } else {
            //Errors
            std::cout << "ERROR::SQLite Database:Inserting New Object: " << insertErrorMsg << std::endl;
            std::cout << "ERROR::SQLITE Database:Creating New Table: " << tableError << " " << sqlite3_errmsg(Database) << std::endl;
        } //end if(error == SQLITE_OK)

        sqlite3_finalize(createTableStmt);
    } //end if(error == SQLITE_OK)

}


///////////////////////////////////////////////////
/*!
\brief Sets the data for an object

This is a system function and should never be called by the user.  This function is automatically called in plData objects.

\param data - Updates the database with it's inforamtion
*/
//////////////////////////////////////////////////
bool plDatabase::SetData(plData* data) {

    //Don't run when object writing is disabled
    if (CantWrite) { return false; }


    //If the object has no owner
    if (!data->IsSetup()) {
        SetupError("SetData", data);
        return false;
    }

    //Get the table and column
    std::string table = data->GetTableName();
    std::string column = data->GetColumnName();

    //Get the object
    plObject* object = data->GetOwner();
    sqlite_int64 id = object->Id();

    std::stringstream sql;
    sql << "UPDATE " << table << " SET " << column << "= ?  WHERE Id=" << id;

    //std::cout << (const char*)sql.toAscii() << std::endl;

    //Setup the sql statement
    sqlite3_stmt* statement;
    int error = sqlite3_prepare_v2(Database, sql.str().c_str(), -1, &statement, NULL);

    if (error == SQLITE_OK) {
        //Bind the statement with the data
        data->Bind(statement);

        //Run the statement
        sqlite3_step(statement);
    } else {

        sqlite3_finalize(statement);

        error = AddColumn(data);

        //If column was added
        if (error == SQLITE_OK) {
            sqlite3_stmt* tryAgainStatement;
            error = sqlite3_prepare_v2(Database, sql.str().c_str(), -1, &tryAgainStatement, NULL);
            if (error == SQLITE_OK) {
                data->Bind(tryAgainStatement);

                sqlite3_step(tryAgainStatement);
                sqlite3_finalize(tryAgainStatement);
                return true;
            }
            sqlite3_finalize(tryAgainStatement);
        }

        //Make sure the database hasn't errored
        if (error != SQLITE_OK) {
            //Error message
            std::cout << "Error:SQLite Database: Failed to run: " << sql << " " << sqlite3_errmsg(Database) << std::endl;
            sqlite3_finalize(statement);
            return false;
        }
    } //end if(error == SQLITE_OK)

    sqlite3_finalize(statement);
    return true;
}

////////////////////////////////////////////////////////////
/*!
\brief Gets the data from the database

This is a system function and should never be called by the user.  This function is automatically called in plData objects.

\param data The database will populate this data with information
 */
////////////////////////////////////////////////////////////
bool plDatabase::GetData(plData* data) const {

    //if (CantWrite) { return false; }

    //If the object has no owner
    if (!data->IsSetup()) {
        SetupError("GetData", data);
        return false;
    }

    //Get the table and column
    std::string table = data->GetTableName();
    std::string column = data->GetColumnName();

    //Get the object
    plObject* object = data->GetOwner();
    sqlite_int64 id = object->Id();

    std::stringstream sql;
    sql << "SELECT " << column << " FROM " << table << " WHERE Id=" << id;

    //std::cout << "GetData:" << sql.str() << std::endl;

    //Setup the sql statement
    sqlite3_stmt* statement;
    int error = sqlite3_prepare_v2(Database, sql.str().c_str(), -1, &statement, NULL);

    //If prepare statement worked
    if (error == SQLITE_OK) {
        //If the step statement worked
        if (sqlite3_step(statement) == SQLITE_ROW) {
            //Sets the data
            data->Column(statement);
        } //if(sqlite3_step(statement) == SQLITE_ROW)
    } else {

        //Error message
        std::cout << "Error:SQLite Database: Failed to run: " << sql << " " << sqlite3_errmsg(Database) << std::endl;
        sqlite3_finalize(statement);
        return false;
    } //end if(error == SQLITE_OK)

    sqlite3_finalize(statement);
    return true;

} //end GetData(plData* data);

///////////////////////////////////////////////////
/*!
\brief Starts a new transaction

For performance reasons it's good to put all function that set data in a transaction block.

Example:
\code

//Starts the transaction
Database->Begin("Test");

//Set some data
myObj->SetExampleValue(10);
myObj->SetExampleValue(100);

//End the transaction
Database->End("Test");

//Getting data is just as fast outside of transactions as in them.
int value = myObj->GetExampleValue();

\endcode

\param name Name of the transaction. This parameter is optional.  It may help you debug code when the library is built with debugging on.  It does nothing if build in release.
*/
///////////////////////////////////////////////////
void plDatabase::Begin(const std::string& name) {

    //For debugging
    if (InTransaction) {
        std::cout << "Error: You forgot to call End() on " << TransactionName << std::endl;
    }

    InTransaction = true;
    TransactionName = name;
    const char* beginSQL = "BEGIN TRANSACTION";
    sqlite3_exec(Database, beginSQL, NULL, NULL, NULL);
}

///////////////////////////////////////////////////
/*!
\brief Ends a transaction

See Begin() for details.

\param name Name of the transaction. Helpful for debugging.
*/
///////////////////////////////////////////////////
void plDatabase::End(const std::string& name) {

    //For debugging
    if (!InTransaction) {
        std::cout << "Error: You forgot to call Begin() on " << name << std::endl;
    }

    InTransaction = false;
    const char* endSQL = "END TRANSACTION";
    sqlite3_exec(Database, endSQL, NULL, NULL, NULL);
}


/////////////////////////////////////////////////
/*!
    \brief Gets the current global database

    \return plDatabase* The current global database
 */
/////////////////////////////////////////////////
plDatabase* plDatabase::GetDatabase() {
    return GlobalDatabase;
}

//Puts and index on the tables column
/*
void plDatabase::Index(const plData& data) {
        plObject* object = data.GetOwner();
        if(!object) {
                std::cout << "ERROR Can't GetData: You forgot to call the Setup(\"name\", owner) on " << data << std::endl;
                return false;
        }

        std::string table = object->_GetClassName();
        std::string column = data->GetColumnName();
        std::string sql;
        QTextStream(&sql) << "CREATE INDEX";


}
*/


////////////////////////////////////////////////////
/*!
\brief Get sqlite3 database

Gets the backend sqlite3 database.  Modifing the backend database is DANGEROUS.  Don't do it usless you know what you are doing.

*/
///////////////////////////////////////////////////
sqlite3* plDatabase::GetSqliteDB() {
    return Database;
}

//////////////////////////////////////// Protected /////////////////////////////////////////

/*!
  \brief Adds the object to the database's memory table
  \param object The object that will be added to the database's memory
  */
void plDatabase::AddToMemory(plObject* object) {
    //Add a memory record to the database
    if (object->Id() == plObject::BadId) {
        //Creates a new memory block in the database
        plMemory* memory = new plMemory();
        memory->SetObject(object);
        memory->DeleteMemory();
    } else {
        //The memory record already exist.  We need to update the name of the memory
        //block to repersent what the object really is.  This function is called from the
        //object's constructor.  In c++ constructor cascade into the base class constructor.
        //For example: A Base class, B sub class of A.  When the user calls A* obj = new A()
        //First the B's constructor is called then A's constructor is called.. Since we only
        //want A's class name we only need we just need to update the memory

        std::stringstream sql;
        sql << "UPDATE plMemory SET ClassName=? WHERE ObjectId=?";

        sqlite3_stmt* statement;
        int error = sqlite3_prepare_v2(Database, sql.str().c_str(), -1, &statement, NULL);
        if (error == SQLITE_OK) {

            const char* className = object->_GetClassName();
            long long id = object->Id();

            sqlite3_bind_text(statement, 1, className, -1, SQLITE_STATIC);
            sqlite3_bind_int64(statement, 2, id);

            sqlite3_step(statement);

            sqlite3_reset(statement);
        }

        sqlite3_finalize(statement);
    }
}

/*!
\brief Updates memory table with the object's memory address
*/
void plDatabase::UpdateMemory(plObject* object, void* realAddress) {

    //Make sure the
    if (object->Id() == plObject::BadId) {
        std::cout << "Internal Database Error: Trying to update memory with a bad ID - Please submit a bug report." << std::endl;
        abort();
    }

    //--- Maybe do Type checking for debugging --

    std::stringstream sql;
    sql << "UPDATE plMemory SET Address=? WHERE ObjectId=?";

    sqlite3_stmt* statement;
    int error = sqlite3_prepare_v2(Database, sql.str().c_str(), -1, &statement, NULL);
    if (error == SQLITE_OK) {

        //We need to use void* because plObject* doesn't pass the correct pointer, with multpile inheridance
        long long address = (long long)realAddress;
        long long id = object->Id();

        sqlite3_bind_int64(statement, 1, address);
        sqlite3_bind_int64(statement, 2, id);

        sqlite3_step(statement);

        sqlite3_reset(statement);
    }

    sqlite3_finalize(statement);

}

///////////////////////////////////////////////////////////////
/*!
  \brief Deletes the object from it's table

    This functions does nothing if ReadOnly() is true.  This function doesn't remove the object from the memory table.  See RemoveFromMemory().

  \param object The object that will be deleted
  */
///////////////////////////////////////////////////////////
void plDatabase::DeleteObject(plObject* object) {

    if (CantWrite) { return; }

    const char* className = object->_GetClassName();
    sqlite_int64 id = object->Id();

    std::stringstream sql;
    sql << "DELETE FROM " << className << " WHERE Id= " << id;

    sqlite3_stmt* statement;
    int error = sqlite3_prepare_v2(Database, sql.str().c_str(), -1, &statement, NULL);
    if (error == SQLITE_OK) {
        sqlite3_step(statement);
    }

    sqlite3_finalize(statement);
}

////////////////////////////////////////////////////////////////
/*!
  \brief Deletes the object from the memory table

  This function does nothing if ReadOnly() is true. This function doesn't remove the object from it's table or it's base class's tables

  */
//Delete the object from memory table
////////////////////////////////////////////////////////////////
void plDatabase::RemoveFromMemory(plObject* object) {

    if (CantWrite) { return; }

    sqlite_int64 id = object->Id();

    std::stringstream sql;
    sql << "DELETE FROM plMemory WHERE ObjectId=" << id;

    sqlite3_stmt* statement;
    int error = sqlite3_prepare_v2(Database, sql.str().c_str(), -1, &statement, NULL);
    if (error == SQLITE_OK) {
        sqlite3_step(statement);
    }

    sqlite3_finalize(statement);
}


///////////////////////////////////////////////////////////////
/*!
\brief Writes NULLs to all rows in the memory table

When the a new database file is open the memory table needs to be reset so it can property do book keeping on objects memory address
*/
///////////////////////////////////////////////////////////////
int plDatabase::ResetMemory() {
    sqlite3_exec(Database, "BEGIN TRANSACTION", NULL, NULL, NULL);
    sqlite3_exec(Database, "UPDATE plMemory SET Address = 0", NULL, NULL, NULL);
    return sqlite3_exec(Database, "END TRANSACTION", NULL, NULL, NULL);
}

///////////////////////////////////////////////////////////////
/*!
\brief Disables/Enables the database

Objects can still read out of the database, but objects can't be added/delete and data can't be modified in the database.

For example:
\code

//This will add MyObj to the database
MyObj* myObj1 = new MyObj();  //Subclass of plObject
myObj1->SetMyValue(10.0); //Set a column in the table to 10.0

Database->ReadOnly(true); //Disable the database

//Delete the object from memory and not from the database
delete myObj1;

//myObj2 still exist on memory but isn't created in the databases
MyObj* myObj2 = new MyObj();
myObj2->SetMyValue(20.0); //Table isn't updated

Database->ReadOnly(false); //Enable the database

//NEVER do this!!! myObj2 was created in the readonly context, so only use it in the readonly context.  This line will have unexpected results
myObj2->SetMyValue(10.0);

\endcode

*/
///////////////////////////////////////////////////////////////
void plDatabase::ReadOnly(bool readonly) {
    CantWrite = readonly;
}

//!True if the database is in a readonly state, otherwise it returns false
bool plDatabase::IsReadOnly() {
    return CantWrite;
}

//////////////////////////////////////////////////////////////
/*!
\brief Alters the table to have the column
\param data The column that will be added
\return int The sqlite3 error code
*/
////////////////////////////////////////////////////////////
int plDatabase::AddColumn(plData* data) {

    //Tables information
    std::string table = data->GetTableName();
    std::string column = data->GetColumnName();

    std::stringstream sql;
    sql << "ALTER TABLE " << table << " ADD COLUMN " << column;

    sqlite3_stmt* statement;
    int error = sqlite3_prepare_v2(Database, sql.str().c_str(), -1, &statement, NULL);

    //If prepare statement worked
    if (error == SQLITE_OK) {
        //Execute the alter table
        sqlite3_step(statement);
    } else {
        std::cout << "Error:SQLite Database: Failed to run: " << sql << " " << sqlite3_errmsg(Database) << std::endl;
    }

    sqlite3_finalize(statement);
    return error;

} //end AddColumn

//////////////////////////////////////////////////////////////////////////////
/*!
 \brief Guess the owner of the data based on memory address

Tries to find the object with the neariest pointer. This function is very dangerous!  Bacisilly it guess the object based only the plData* location.  This function isn't avalible in release mode, but is in debugging mode

\return plObject* Can be NULL if didn't find any matches
*/
/////////////////////////////////////////////////////////////////////////////
plObject* plDatabase::GetNearestObject(void* data) const {

    std::stringstream sql;
    sql << "SELECT Address FROM plMemory WHERE Address<=" << (long long)data << " ORDER BY Address DESC";

    sqlite3_stmt* statement;
    int error = sqlite3_prepare_v2(Database, sql.str().c_str(), -1, &statement, NULL);

    //If prepare was okay
    if (error == SQLITE_OK) {

        if (sqlite3_step(statement) == SQLITE_ROW) {

            long long address = sqlite3_column_int64(statement, 0);
            //const char* className = (const char*)sqlite3_column_text(statement, 1);

            //Sorta scary cast
            return static_cast<plObject*>((void*)address);
        }
    }

    return NULL;
}

/////////////////////////////////////////////////////////////////////////////
/*!
\brief Output error message when plObject::Setup() wasn't calleds
*/
////////////////////////////////////////////////////////////////////////////
void plDatabase::SetupError(const char* functionCall, plData* data) const {
    plObject* errorObject = GetNearestObject(data);
    const char* className = "Unknown className";
    if (errorObject) {
        className = errorObject->_GetClassName();
    }
    long long distance = (((long long)data) - ((long long)errorObject)) / 8;
    std::cout << "ERROR Can't " << functionCall << ": You forgot to call the Setup(\"name\", owner) on member data " << data << " in " << className << " (Maybe?) " << "Distance of: " << distance << " byte(s)" << std::endl;
}


